﻿using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.SpecializedProperties;
using EPiServer.UI.WebControls;
using EPiServer.Web.WebControls;
using TemplateFoundation.Core;
using TemplateFoundation.Exceptions;
using TemplateFoundation.Serialization;
using TemplateFoundation.UI.WebControls;

namespace TemplateFoundation.WebControls.PropertyControls
{
    /// <summary>
    /// Loads the correct user control as the property control depending on the render type
    /// </summary>
    /// <typeparam name="T">The type of the property value</typeparam>
    public class PropertyUserControlDispatcher<T> : WebControl, IPropertyControl, INamingContainer  where T : class
    {
        private PropertyUserControlBase<T> _propertyUserControl;

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyUserControlDispatcher&lt;T&gt;"/> class.
        /// </summary>
        public PropertyUserControlDispatcher() : this("","","")
        {
            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyUserControlDispatcher&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="editUserControlPath">The edit user control path.</param>
        /// <param name="defaultUserControlPath">The default user control path.</param>
        /// <param name="onPageEditUserControlPath">The on page edit user control path.</param>
        public PropertyUserControlDispatcher(string editUserControlPath, string defaultUserControlPath, string onPageEditUserControlPath) : base("")
        {
            SetDefaultPropertyValues(editUserControlPath, defaultUserControlPath, onPageEditUserControlPath);
        }

        /// <summary>
        /// Sets the default property values.
        /// </summary>
        /// <param name="editUserControlPath">The edit user control path.</param>
        /// <param name="defaultUserControlPath">The default user control path.</param>
        /// <param name="onPageEditUserControlPath">The on page edit user control path.</param>
        protected virtual void SetDefaultPropertyValues(string editUserControlPath, string defaultUserControlPath, string onPageEditUserControlPath)
        {
            OnPageEditUserControlPath = onPageEditUserControlPath;
            DefaultUserControlPath = defaultUserControlPath;
            EditUserControlPath = editUserControlPath;

            // Set default validation message
            ValidationMessage = LanguageManager.Instance.Translate("/usercontrolproperties/defaultvalidationmessage");
        }

        #region Properties
        /// <summary>
        /// Gets or sets the on page edit user control path.
        /// </summary>
        /// <value>The on page edit user control path.</value>
        public string OnPageEditUserControlPath { get; set; }
        /// <summary>
        /// Gets or sets the edit user control path.
        /// </summary>
        /// <value>The edit user control path.</value>
        public string EditUserControlPath { get; set; }
        /// <summary>
        /// Gets or sets the default user control path.
        /// </summary>
        /// <value>The default user control path.</value>
        public string DefaultUserControlPath { get; set; }

        /// <summary>
        /// Gets or sets the value from the user control to save as the property value
        /// </summary>
        public virtual T Value
        {
            get
            {
                return _propertyUserControl.Value;
            }
        }

        /// <summary>
        /// Gets the validation message to display if the property value cannot be saved
        /// </summary>
        protected virtual string ValidationMessage { get; set; }

        /// <summary>
        /// Returns the PropertyDataForm control in which the user control resides
        /// </summary>
        protected PropertyDataForm PropertyDataForm
        {
            get
            {
                Control propertyParent = Parent;

                while (!(propertyParent is PropertyDataForm) && propertyParent!=null)
                {
                    propertyParent = propertyParent.Parent;
                }

                return (PropertyDataForm)propertyParent;
            }
        }
        #endregion

        /// <summary>
        /// Creates the on page edit controls by adding the appropriate user control
        /// </summary>
        protected virtual void CreateOnPageEditControls()
        {
            // Load the user control (if specified) for supporting on-page edit

            if (string.IsNullOrEmpty(OnPageEditUserControlPath))
            {
                throw new NotSupportedException("This property does not support on-page editing");
            }

            AddPropertyUserControl(OnPageEditUserControlPath);
        }

        /// <summary>
        /// Creates the edit controls by adding the appropriate user control
        /// </summary>
        protected virtual void CreateEditControls()
        {
            // Load the user control (if specified) for rendering the property value on page templates

            if (string.IsNullOrEmpty(EditUserControlPath))
            {
                Controls.Add(new Literal { Text = LanguageManager.Instance.Translate("/usercontrolproperties/editcontrolnotset") });
                return;
            }

            AddPropertyUserControl(EditUserControlPath);
        }

        /// <summary>
        /// Creates default controls by adding the appropriate user control
        /// </summary>
        protected virtual void CreateDefaultControls()
        {
            // Load the user control (if specified) for rendering the property value on page templates

            if(string.IsNullOrEmpty(DefaultUserControlPath))
            {
                Controls.Add(new Literal { Text = LanguageManager.Instance.Translate("/usercontrolproperties/defaultcontrolnotset") });
                return;
            }

            AddPropertyUserControl(DefaultUserControlPath);
        }

        /// <summary>
        /// Adds a user control based on the specified virtual path to its .ascx file
        /// </summary>
        /// <param name="virtualPath">Virtual path to the user control's .ascx file</param>
        protected virtual void AddPropertyUserControl(string virtualPath)
        {
            // Verify user control type

            try
            {
                _propertyUserControl = (PropertyUserControlBase<T>)Page.LoadControl(virtualPath);
            }
            catch (InvalidCastException ex)
            {
                throw new TemplateFoundationException("Unable to cast property user control to correct type. Make sure it inherits PropertyUserControlBase.", ex);
            }
            
            _propertyUserControl.ID = "PropertyUserControl";

            if (PropertyData!=null && !PropertyData.IsNull)
            {
                try
                {
                    var str = PropertyData.Value as string;

                    if(!string.IsNullOrEmpty(str))
                    {
                        _propertyUserControl.Value = (T)ConvertValueFromXhtmlString(str);
                    }
                }
                catch
                {
                    Controls.Add(new ValidationErrorMessage { Text = LanguageManager.Instance.Translate("/usercontrolproperties/deserializationerror") });
                }
            }

            // Trigger PropertyLoad event to have user control set up on first load (either by direct load or postback between Preview and Edit tab)
            if (!Page.IsPostBack || Page.GetPostBackControl() is Tab)
            {
                _propertyUserControl.Load += delegate { _propertyUserControl.OnPropertyLoad(new EditPageEventArgs(SaveAction.None)); };
            }

            _propertyUserControl.PropertyData = this.PropertyData;

            Controls.Add(_propertyUserControl);

            _propertyUserControl.DataBind();
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event which takes care of setting the custom property Enabled state
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            // HACK 'Enabled' property is (sometimes) false even though IsLanguageSpecific==true
            if (!Enabled && (PropertyData!=null && !PropertyData.IsLanguageSpecific))
            {
                SetEnabledState(this, false);
            }
            else
            {
                SetEnabledState(this, true);
            }
        }

        // No container element required so we skip RenderBeginTag and RenderEndTag

        /// <summary>
        /// Renders the HTML opening tag of the control to the specified writer. This method is used primarily by control developers.
        /// </summary>
        /// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
        public override void RenderBeginTag(HtmlTextWriter writer) { }

        /// <summary>
        /// Renders the HTML closing tag of the control into the specified writer. This method is used primarily by control developers.
        /// </summary>
        /// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
        public override void RenderEndTag(HtmlTextWriter writer) { }

        #region PropertyDataControl members

        /// <summary>
        /// Gets a value indicating whether on-page editing is supported by the property
        /// </summary>
        /// <value><c>true</c> if on-page editing is supported, otherwise <c>false</c>.</value>
        public virtual bool SupportsOnPageEdit
        {
            get { return !string.IsNullOrEmpty(OnPageEditUserControlPath); }
        }

        private IPropertyControl GetAdapterAsIPropertyControl()
        {
            if (Adapter == null)
            {
                return null;
            }
            return (Adapter as IPropertyControl);
        }

        /// <summary>
        /// Applies the edit changes.
        /// </summary>
        public void ApplyEditChanges()
        {
            SetValue(Value);
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="value">The value.</param>
        public void SetValue(object value)
        {
            SetValue(value, PropertyData);
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="property">The property.</param>
        public void SetValue(object value, PropertyData property)
        {
            try
            {
                // Trigger PropertySave event to have Value property properly set by user control
                _propertyUserControl.OnPropertySave(new EditPageEventArgs(SaveAction.Save));

                property.Value = ConvertValueToXhtmlString(_propertyUserControl.Value);

                // Trigger PropertyLoad if Save button was clicked (which reloads the Edit tab)
                if(Page.GetPostBackControl().ID=="SaveOnly")
                {
                    _propertyUserControl.OnPropertyLoad(new EditPageEventArgs(SaveAction.Save));
                }
            }
            catch (Exception exception)
            {
                AddErrorValidator(exception.Message);
            }
        }

        /// <summary>
        /// Converts the property value to a string in order to save it as a PropertyXhtmlString value
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        protected virtual string ConvertValueToXhtmlString(T source)
        {
            if (source is LinkItemCollection)
            {
                return source.ToString();
            }
            
            // All other types are saved as serialized XML
            return XmlSerializer.Serialize(_propertyUserControl.Value);
        }

        /// <summary>
        /// Converts the property value from its string representation
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        protected virtual object ConvertValueFromXhtmlString(string source)
        {
            if(typeof(T) == typeof(LinkItemCollection))
            {
                var links = new PropertyLinkCollection();

                links.LoadData(source);

                return links.Links;
            }

            // All other types have been serialized to XML
            return XmlSerializer.Deserialize<T>(source);
        }

        /// <summary>
        /// Adds a validator control if the property value couldn't be saved
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <remarks>To change the validator message, override the ValidatorMessage property</remarks>
        public virtual void AddErrorValidator(string errorMessage)
        {
            var validator = new StaticValidator(errorMessage);
            validator.Text = ValidationMessage;
            validator.ValidationGroup = ValidationGroup;

            var container = new HtmlGenericControl("div");
            container.Attributes.Add("class","EP-validationSummary");
            container.Controls.Add(validator);

            var ul = new HtmlGenericControl("ul");

            var li = new HtmlGenericControl("li");

            var style = new HtmlGenericControl("style");
            style.Attributes.Add("type", "text/css");
            style.InnerText = "span.errormessage { color: black !important }";

            li.Controls.Add(validator);

            ul.Controls.Add(li);

            container.Controls.Add(ul);

            container.Controls.Add(style);

            Controls.AddAt(0, container);
        }

        #endregion
   
        #region IPropertyControl Members

        /// <summary>
        /// Applies property changes
        /// </summary>
        public void ApplyChanges()
        {
            var adapterAsIPropertyControl = GetAdapterAsIPropertyControl();

            if (adapterAsIPropertyControl != null)
            {
                adapterAsIPropertyControl.ApplyChanges();
            }
            else if (RenderType == RenderType.Edit)
            {
                ApplyEditChanges();
            }
        }

        /// <summary>
        /// Gets a value indicating whether the UI should be displayed
        /// </summary>
        public bool DisplayEditUI
        {
            get
            {
                IPropertyControl adapterAsIPropertyControl = GetAdapterAsIPropertyControl();
                if (adapterAsIPropertyControl != null)
                {
                    return adapterAsIPropertyControl.DisplayEditUI;
                }
                return PropertyData.DisplayEditUI;
            }

        }

        /// <summary>
        /// Gets or sets the properties.
        /// </summary>
        /// <value>The properties.</value>
        public PropertyDataCollection Properties
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the property data.
        /// </summary>
        /// <value>The property data.</value>
        public PropertyData PropertyData { get; set; }

        /// <summary>
        /// Gets or sets the type of the render.
        /// </summary>
        /// <value>The type of the render.</value>
        public RenderType RenderType
        {
            get; set;
        }

        /// <summary>
        /// Gets the row layout (default is Wide)
        /// </summary>
        /// <value>The row layout.</value>
        public TableRowLayout RowLayout
        {
            get
            {
                IPropertyControl adapterAsIPropertyControl = GetAdapterAsIPropertyControl();
                if (adapterAsIPropertyControl != null)
                {
                    return adapterAsIPropertyControl.RowLayout;
                }
                return TableRowLayout.Wide;
            }
        }

        /// <summary>
        /// Sets up the property control
        /// </summary>
        public void SetupControl()
        {
            // Load the relevant user control depending on the render type

            if (RenderType==RenderType.Edit)
            {
                CreateEditControls();

                // Disable control if property should be disabled for the current language
                Enabled = !(PropertyDataForm.DisableNonLanguageSpecific && !PropertyData.IsLanguageSpecific);
            }
            else if ((RenderType == RenderType.OnPageEdit) && SupportsOnPageEdit)
            {
                ValidationGroup = "onpageeditcontrols";
                CreateOnPageEditControls();
            }
            else
            {
                CreateDefaultControls();
            }
        }

        /// <summary>
        /// Gets or sets the validation group, if any
        /// </summary>
        /// <value>The validation group.</value>
        public string ValidationGroup { get; set; }

        #endregion

        #region Helper methods

        /// <summary>
        /// Enables/disables a control and all of its child controls recursively
        /// </summary>
        /// <param name="control"></param>
        /// <param name="state"></param>
        protected void SetEnabledState(Control control, bool state)
        {
            foreach (Control childControl in control.Controls)
            {
                SetEnabledState(childControl, state);
            }

            if (control is WebControl)
            {
                ((WebControl)control).Enabled = state;
            }
            else if (control is HtmlControl)
            {
                ((HtmlControl)control).Disabled = !state;
            }
        }

        #endregion
    }

    /// <summary>
    /// Loads the correct user control as the property control depending on the render type
    /// </summary>
    public class PropertyUserControlDispatcher : PropertyUserControlDispatcher<string>
    {

    }
}
